/*
 * File     : SunMiscUnsafeAccess.java
 * Created  : 15 Apr 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.logging.Level;

import sun.misc.Unsafe;

import com.googlecode.dni.Allocator;
import com.googlecode.dni.internal.MemoryAccess.MemoryAccessor;
import com.googlecode.dni.internal.platform.PlatformUtilities;
import com.googlecode.dni.type.Pointer;

/**
 * Wrapper around <code>sun.misc.Unsafe</code>.  Do not cause this class to be
 * loaded unless <code>sun.misc.Unsafe</code> is available.
 *
 * @author Matthew Wilson
 */
@SuppressWarnings( "nls" )
public final class SunMiscUnsafeAccess
{

    private static final Unsafe UNSAFE;

    private static final boolean BUFFER_ADDRESS_ACCESSIBLE;

    private static final long BUFFER_ADDRESS_OFFSET;

    private static final Constructor< ? > BUFFER_CONSTRUCTOR;

    static
    {
        UNSAFE = (Unsafe) PlatformUtilities.getSunMiscUnsafe();
        if ( UNSAFE == null )
        {
            throw new ExceptionInInitializerError( "You must disable the use of sun.misc.Unsafe by DNI" );
        }

        boolean accessible = false;
        long offset = 0;
        try
        {
            Field field = Buffer.class.getDeclaredField( "address" );
            if ( field.getType() == long.class )
            {
                offset = UNSAFE.objectFieldOffset( field );
                accessible = true;
            }
        }
        catch ( Exception exception )
        {
            // ignore
            DniInternal.LOGGER.log( Level.FINE, "Won't be able to use optimised Buffer.address access", exception );
        }
        BUFFER_ADDRESS_ACCESSIBLE = accessible;
        BUFFER_ADDRESS_OFFSET = offset;

        Constructor< ? > bufferConstructor = null;
        try
        {
            bufferConstructor = Class.forName( "java.nio.DirectByteBuffer" ).getDeclaredConstructor( long.class, int.class );
            bufferConstructor.setAccessible( true );
        }
        catch ( Exception exception )
        {
            // ignore
            DniInternal.LOGGER.log( Level.FINE, "Won't be able to use optimised Buffer constructor access", exception );
            bufferConstructor = null;
        }
        BUFFER_CONSTRUCTOR = bufferConstructor;
    }

    /** Prevents instantiation. */
    private SunMiscUnsafeAccess()
    {
        throw new UnsupportedOperationException();
    }

    /**
     * @return an allocator instance
     */
    public static Allocator getAllocator()
    {
        return new AllocatorImpl();
    }

    /**
     * @return an accessor impl
     */
    public static MemoryAccessor getMemoryAccessor()
    {
        return new MemoryAccessorImpl();
    }

    /**
     * Allocates memory.
     *
     * @param size
     *            the size in bytes
     * @return pointer to the memory
     */
    public static long allocateMemory( final long size )
    {
        return UNSAFE.allocateMemory( size );
    }

    /**
     * Copies memory.
     *
     * @param from
     *            pointer to the source
     * @param to
     *            pointer to the destination
     * @param size
     *            the size in bytes
     */
    public static void copyMemory( final long from, final long to, final long size )
    {
        UNSAFE.copyMemory( from, to, size );
    }

    /**
     * Frees memory.
     *
     * @param pointer
     *            pointer to the memory
     */
    public static void freeMemory( final long pointer )
    {
        UNSAFE.freeMemory( pointer );
    }

    /**
     * @param pointer
     *            the pointer
     * @return the boolean value
     */
    public static boolean getBoolean( final long pointer )
    {
        return UNSAFE.getBoolean( null, pointer );
    }

    /**
     * @param pointer
     *            the pointer
     * @return the byte value
     */
    public static byte getByte( final long pointer )
    {
        return UNSAFE.getByte( null, pointer );
    }

    /**
     * @param pointer
     *            the pointer
     * @return the char value
     */
    public static char getChar( final long pointer )
    {
        return UNSAFE.getChar( null, pointer );
    }

    /**
     * @param pointer
     *            the pointer
     * @return the double value
     */
    public static double getDouble( final long pointer )
    {
        return UNSAFE.getDouble( null, pointer );
    }

    /**
     * @param pointer
     *            the pointer
     * @return the float value
     */
    public static float getFloat( final long pointer )
    {
        return UNSAFE.getFloat( null, pointer );
    }

    /**
     * @param pointer
     *            the pointer
     * @return the int value
     */
    public static int getInt( final long pointer )
    {
        return UNSAFE.getInt( null, pointer );
    }

    /**
     * @param pointer
     *            the pointer
     * @return the long value
     */
    public static long getLong( final long pointer )
    {
        return UNSAFE.getLong( null, pointer );
    }

    /**
     * @param pointer
     *            the pointer
     * @return the short value
     */
    public static short getShort( final long pointer )
    {
        return UNSAFE.getShort( null, pointer );
    }

    /**
     * @param pointer
     *            the pointer
     * @param value
     *            the boolean value
     */
    public static void putBoolean( final long pointer, final boolean value )
    {
        UNSAFE.putBoolean( null, pointer, value );
    }

    /**
     * @param pointer
     *            the pointer
     * @param value
     *            the byte value
     */
    public static void putByte( final long pointer, final byte value )
    {
        UNSAFE.putByte( null, pointer, value );
    }

    /**
     * @param pointer
     *            the pointer
     * @param value
     *            the char value
     */
    public static void putChar( final long pointer, final char value )
    {
        UNSAFE.putChar( null, pointer, value );
    }

    /**
     * @param pointer
     *            the pointer
     * @param value
     *            the double value
     */
    public static void putDouble( final long pointer, final double value )
    {
        UNSAFE.putDouble( null, pointer, value );
    }

    /**
     * @param pointer
     *            the pointer
     * @param value
     *            the boolean value
     */
    public static void putFloat( final long pointer, final float value )
    {
        UNSAFE.putFloat( null, pointer, value );
    }

    /**
     * @param pointer
     *            the pointer
     * @param value
     *            the int value
     */
    public static void putInt( final long pointer, final int value )
    {
        UNSAFE.putInt( null, pointer, value );
    }

    /**
     * @param pointer
     *            the pointer
     * @param value
     *            the long value
     */
    public static void putLong( final long pointer, final long value )
    {
        UNSAFE.putLong( null, pointer, value );
    }

    /**
     * @param pointer
     *            the pointer
     * @param value
     *            the short value
     */
    public static void putShort( final long pointer, final short value )
    {
        UNSAFE.putShort( null, pointer, value );
    }

    /**
     * Sets memory to the given value.
     *
     * @param pointer
     *            the pointer
     * @param length
     *            the number of bytes
     * @param value
     *            the value to set
     */
    public static void setMemory( final long pointer, final long length, final byte value )
    {
        UNSAFE.setMemory( pointer, length, value );
    }

    /**
     * Obtains the pointer to the base of the given buffer's memory.
     *
     * @param buffer
     *            the buffer
     * @return the pointer
     */
    public static long getBufferPointer( final Buffer buffer )
    {
        if ( !buffer.isDirect() )
        {
            throw new IllegalArgumentException();
        }

        long address;
        if ( BUFFER_ADDRESS_ACCESSIBLE )
        {
            address = UNSAFE.getLong( buffer, BUFFER_ADDRESS_OFFSET );
        }
        else
        {
            address = NativeUnsafeAccess.getBufferPointer( buffer );
        }

        if ( address == 0L )
        {
            throw new IllegalArgumentException();
        }

        return address;
    }

    /**
     * Creates a buffer (native endian).
     *
     * @param pointer
     *            the pointer
     * @param size
     *            the size in bytes
     * @return a buffer
     */
    public static ByteBuffer getBuffer( final long pointer, final int size )
    {
        if ( size < 0 )
        {
            throw new IllegalArgumentException();
        }

        if ( BUFFER_CONSTRUCTOR != null )
        {
            try
            {
                ByteBuffer buffer = (ByteBuffer) BUFFER_CONSTRUCTOR.newInstance( pointer, size );
                return buffer.order( DniInternal.NATIVE_ENDIAN );
            }
            catch ( Exception exception )
            {
                throw (AssertionError) new AssertionError( "You must disable use of sun.misc.Unsafe for your JVM" ).initCause( exception );
            }
        }

        return NativeUnsafeAccess.getBuffer( pointer, size ).order( DniInternal.NATIVE_ENDIAN );
    }

    private static final class AllocatorImpl implements Allocator
    {
        @Override
        public Pointer allocate( final long size )
        {
            long pointer = allocateMemory( size );
            if ( pointer == 0 )
            {
                throw new OutOfMemoryError();
            }

            setMemory( pointer, size, (byte) 0 );

            return Pointer.fromAddress( pointer );
        }

        @Override
        public Pointer copy( final Pointer sourcePointer, final long size )
        {
            long pointer = allocateMemory( size );
            if ( pointer == 0 )
            {
                throw new OutOfMemoryError();
            }

            copyMemory( sourcePointer.address(), pointer, size );

            return Pointer.fromAddress( pointer );
        }

        @Override
        public void free( final Pointer pointer )
        {
            if ( Pointer.isNull( pointer ) )
            {
                return;
            }
            freeMemory( pointer.address() );
        }
    }

    private static final class MemoryAccessorImpl implements MemoryAccessor
    {
        /** {@inheritDoc} */
        @Override
        public byte getByte( final long pointer )
        {
            return UNSAFE.getByte( pointer );
        }

        /** {@inheritDoc} */
        @Override
        public void putByte( final long pointer, final byte value )
        {
            UNSAFE.putByte( pointer, value );
        }

        /** {@inheritDoc} */
        @Override
        public char getChar( final long pointer )
        {
            return UNSAFE.getChar( pointer );
        }

        /** {@inheritDoc} */
        @Override
        public void putChar( final long pointer, final char value )
        {
            UNSAFE.putChar( pointer, value );
        }

        /** {@inheritDoc} */
        @Override
        public int getInt( final long pointer )
        {
            return UNSAFE.getInt( pointer );
        }

        /** {@inheritDoc} */
        @Override
        public void putInt( final long pointer, final int value )
        {
            UNSAFE.putInt( pointer, value );
        }

        /** {@inheritDoc} */
        @Override
        public long getLong( final long pointer )
        {
            return UNSAFE.getLong( pointer );
        }

        /** {@inheritDoc} */
        @Override
        public void putLong( final long pointer, final long value )
        {
            UNSAFE.putLong( pointer, value );
        }

        /** {@inheritDoc} */
        @Override
        public void putDouble( final long pointer, final double value )
        {
            UNSAFE.putDouble( pointer, value );
        }

        /** {@inheritDoc} */
        @Override
        public long getBufferPointer( final Buffer buffer )
        {
            return SunMiscUnsafeAccess.getBufferPointer( buffer );
        }

        /** {@inheritDoc} */
        @Override
        public ByteBuffer getBuffer( final long pointer, final int size )
        {
            return SunMiscUnsafeAccess.getBuffer( pointer, size );
        }

        /** {@inheritDoc} */
        @Override
        public void copyMemory( final long from, final long to, final long size )
        {
            UNSAFE.copyMemory( from, to, size );
        }

        /** {@inheritDoc} */
        @Override
        public void zeroMemory( final long pointer, final long size )
        {
            UNSAFE.setMemory( pointer, size, (byte) 0 );
        }
    }

}
